package com.zmzncs.nat.channel;

import com.zmzncs.nat.utils.Tools;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class LengthChannel extends SocketChannel<byte[], byte[]> {

    private Socket socket;
    private InputStream inputStream;
    private OutputStream outputStream;

    private ReentrantLock writeLock = new ReentrantLock();
    private ReentrantLock readLock = new ReentrantLock();

    public LengthChannel() {
    }

    public LengthChannel(Socket socket) {
        this.setSocket(socket);
    }

    @Override
    public byte[] read() {
        readLock.lock();

        InputStream is = null;
        byte[] b = new byte[0];
        try {
            is = getInputSteam();
            byte[] len = new byte[4];
            is.read(len);
            int length = Tools.bytes2int(len);

            b = new byte[length];
            is.read(b, 0, length);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }

        return b;
    }

    @Override
    public void write(byte[] value) {
        writeLock.lock();
        try {
            OutputStream os = getOutputStream();
            int length = value.length;
            os.write(Tools.intToBytes(length));
            os.write(value);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
        }
    }

    @Override
    public void flush() {
        writeLock.lock();
        try {
            getOutputStream().flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
        }
    }

    @Override
    public void writeAndFlush(byte[] value) {
        this.write(value);
        this.flush();
    }

    @Override
    public Socket getSocket() {
        return socket;
    }

    @Override
    public void setSocket(Socket socket) {
        this.socket = socket;
        try {
            this.inputStream = this.socket.getInputStream();
            this.outputStream = this.socket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void closeSocket() throws IOException {
        this.socket.close();
    }

    /**
     * 惰性获取输入流
     * 
     * @author Pluto
     * @since 2020-01-08 16:15:32
     * @return
     * @throws IOException
     */
    private InputStream getInputSteam() throws IOException {
        if (this.inputStream == null) {
            return this.socket.getInputStream();
        }
        return this.inputStream;
    }

    /**
     * 惰性获取输出流
     * 
     * @author Pluto
     * @since 2020-01-08 16:15:48
     * @return
     * @throws IOException
     */
    private OutputStream getOutputStream() throws IOException {
        if (this.outputStream == null) {
            return this.getSocket().getOutputStream();
        }
        return this.outputStream;
    }

}
